package ar.edu.sccs.model.loader;

import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import ar.edu.sccs.entities.Case;
import ar.edu.sccs.entities.Dimension;
import ar.edu.sccs.model.loader.dto.DataClazz;
import ar.edu.sccs.model.loader.dto.DataClazzField;
import ar.edu.sccs.model.loader.dto.DataMethod;
import ar.edu.sccs.model.loader.dto.DataMethodParameter;
import ar.edu.sccs.model.loader.dto.DataSCM;
import ar.edu.sccs.model.loader.dto.Data.Visibility;
import ar.edu.sccs.util.Constants;

public class ReflectionLoader {

	static Logger logger = Logger.getLogger(ReflectionLoader.class);

	public static DataClazz getClassInfo(File clazzFile, File clazzDirectory) {
		Class clazz;
		ClassLoader clazzLoader = java.lang.ClassLoader.getSystemClassLoader();

		try {
			//Obtengo Path absolutos
			String clazzFileAbsolutePath = clazzFile.getAbsolutePath();
			String clazzDirectoryAbsolutePath = clazzDirectory.getAbsolutePath();

			//Obtengo el Path absoluto desde el directorion BIN como raiz
			String clazzCanonicalName = clazzFileAbsolutePath.substring(clazzDirectoryAbsolutePath.length() + 1, clazzFileAbsolutePath.lastIndexOf("."));
			//Reemplazo los separadores de directorios por '.'
			clazzCanonicalName = clazzCanonicalName.replace(System.getProperty("file.separator"), ".");

			clazz = clazzLoader.loadClass(clazzCanonicalName);
			DataClazz dataClazz = getClassInfo(clazz);
			dataClazz.setClazzRepository(new DataSCM(clazzFileAbsolutePath));
			return dataClazz;

		} catch (Exception e) {
			logger.error("ReflectionLoader.getClassInfo ERROR", e);
			return null;
		}
	}
	
	private static DataClazz getClassInfo(Class clazz) {
		DataClazz dataClazz = new DataClazz();
		
		// CLASS.PACKAGE
		dataClazz.setClazzPackage(clazz.getPackage().getName());
		// CLASS.NAME
		dataClazz.setClazzName(clazz.getSimpleName());
		// CLASS.EXTEND
		dataClazz.setClazzExtend(clazz.getEnclosingClass());

		// CLASS.FIELDS
		for (Field clazzField : clazz.getDeclaredFields()) {
			dataClazz.addField(new DataClazzField(clazzField.getName(), clazzField.getType()));
		}

		// CLASS.IMPLEMENTS
		for(Class claszImplement : clazz.getInterfaces()){
			dataClazz.addClazzImplement(claszImplement);
		}

		int clazzModifier = clazz.getModifiers();
		dataClazz.setModifiers(clazzModifier);

		// CLASS.MODIFIERS
		dataClazz.setAbstract((clazzModifier & Modifier.ABSTRACT) == Modifier.ABSTRACT);
		dataClazz.setFinal((clazzModifier & Modifier.FINAL) == Modifier.FINAL);
		dataClazz.setInterface((clazzModifier & Modifier.INTERFACE) == Modifier.INTERFACE);
		dataClazz.setStrictfp((clazzModifier & Modifier.STRICT) == Modifier.STRICT);

		// CLASS.VISIBILITY
		if ((clazzModifier & Modifier.PUBLIC) == Modifier.PUBLIC)
			dataClazz.setVisibility(Visibility.PUBLIC_VISIBILITY);
		else if ((clazzModifier & Modifier.PRIVATE) == Modifier.PRIVATE)
			dataClazz.setVisibility(Visibility.PRIVATE_VISIBILITY);
		else if ((clazzModifier & Modifier.PROTECTED) == Modifier.PROTECTED)
			dataClazz.setVisibility(Visibility.PROTECTED_VISIBILITY);
		else
			dataClazz.setVisibility(Visibility.NOTHING_VISIBILITY);
		
		// METHOD
		for (Method method : clazz.getDeclaredMethods()) {
			DataMethod clazzMethod = new DataMethod();

			// METHOD.NAME
			clazzMethod.setMethodName(method.getName());

			// METHOD.RETURN
			clazzMethod.setMethodReturn(method.getReturnType());

			// METHOD.PARAMETER
			for (Class methodParameter : method.getParameterTypes()) {
				clazzMethod.addMethodParameter(new DataMethodParameter(methodParameter));
			}

			// METHOD.EXCEPTION
			for (Class methodException : method.getExceptionTypes()) {
				clazzMethod.addMethodException(methodException);
			}

			int methodModifier = method.getModifiers();
			clazzMethod.setModifiers(methodModifier);

			// CLASS.MODIFIERS
			clazzMethod.setAbstract((methodModifier & Modifier.ABSTRACT) == Modifier.ABSTRACT);
			clazzMethod.setFinal((methodModifier & Modifier.FINAL) == Modifier.FINAL);
			clazzMethod.setNative((methodModifier & Modifier.NATIVE) == Modifier.NATIVE);
			clazzMethod.setStatic((methodModifier & Modifier.STATIC) == Modifier.STATIC);
			clazzMethod.setSynchronized((methodModifier & Modifier.SYNCHRONIZED) == Modifier.SYNCHRONIZED);
			clazzMethod.setStrictfp((methodModifier & Modifier.STRICT) == Modifier.STRICT);

			// METHOD.VISIBILITY
			if ((methodModifier & Modifier.PUBLIC) == Modifier.PUBLIC)
				clazzMethod.setVisibility(Visibility.PUBLIC_VISIBILITY);
			else if ((methodModifier & Modifier.PRIVATE) == Modifier.PRIVATE)
				clazzMethod.setVisibility(Visibility.PRIVATE_VISIBILITY);
			else if ((methodModifier & Modifier.PROTECTED) == Modifier.PROTECTED)
				clazzMethod.setVisibility(Visibility.PROTECTED_VISIBILITY);
			else
				clazzMethod.setVisibility(Visibility.NOTHING_VISIBILITY);

			dataClazz.addMethod(clazzMethod);
		}

		return dataClazz;
	}

	public static List<Case> loadCaseInfo(List<DataClazz> dataClazz) {
		List<Case> caseList = new ArrayList<Case>();

		for (DataClazz clazz : dataClazz) {
			for (DataMethod method : clazz.getMethods()) {

				Case newCase = new Case();
				newCase.setScmUrl(clazz.getClazzRepository().getSourcePath());
				newCase.setCalification(Constants.Dimension.Calification.SEEN);

				Dimension clazzType = new Dimension(clazz.isInterface() ? "Interface" : "Class");
				newCase.putDimension(Constants.Dimension.Class.TYPE, clazzType);

				Dimension clazzName = new Dimension(clazz.getClazzName());
				newCase.putDimension(Constants.Dimension.Class.NAME, clazzName);

				Dimension clazzPackage = new Dimension(clazz.getClazzPackage());
				newCase.putDimension(Constants.Dimension.Class.PACKAGE, clazzPackage);

				Dimension clazzExtend = new Dimension(clazz.getClazzExtend());
				if (clazz.getClazzExtend() != null)
					clazzExtend = new Dimension(clazz.getClazzExtend().getCanonicalName());
				newCase.putDimension(Constants.Dimension.Class.EXTEND, clazzExtend);

				String clazzImplementsData = "";
				for (Class implmentedClazz : clazz.getClazzImplements()) {
					clazzImplementsData += (implmentedClazz.getName() + "|");
				}
		
				Dimension clazzImplements = new Dimension(clazzImplementsData);
				newCase.putDimension(Constants.Dimension.Class.IMPLEMENTS, clazzImplements);
				
				Dimension clazzIsPublic = new Dimension(clazz.getVisibility() == Visibility.PUBLIC_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PUBLIC, clazzIsPublic);
				
				Dimension clazzIsProtected = new Dimension(clazz.getVisibility() == Visibility.PROTECTED_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PROTECTED, clazzIsProtected);
				
				Dimension clazzIsPrivate = new Dimension(clazz.getVisibility() == Visibility.PRIVATE_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Class.PRIVATE, clazzIsPrivate);
				
				Dimension clazzIsAbstract = new Dimension(clazz.isAbstract());
				newCase.putDimension(Constants.Dimension.Class.ABSTRACT, clazzIsAbstract);
				
				Dimension clazzIsFinal = new Dimension(clazz.isFinal());
				newCase.putDimension(Constants.Dimension.Class.FINAL, clazzIsFinal);
				
				Dimension clazzIsStrictfp = new Dimension(clazz.isStrictfp());
				newCase.putDimension(Constants.Dimension.Class.STRICTFP, clazzIsStrictfp);
				
				Dimension methodName = new Dimension(method.getMethodName());
				newCase.putDimension(Constants.Dimension.Method.NAME, methodName);
				
				Dimension methodReturn = new Dimension(method.getMethodReturn());
				if (method.getMethodReturn() != null)
					methodReturn = new Dimension(method.getMethodReturn().getCanonicalName());
				newCase.putDimension(Constants.Dimension.Method.RETURN, methodReturn);
				
				String methodParamsData = "";
				for (DataMethodParameter methodParameter : method.getMethodParameters()){
					methodParamsData += (methodParameter.getParameterType() + "|");
				}

				Dimension methodParams = new Dimension(methodParamsData);
				newCase.putDimension(Constants.Dimension.Method.PARAMS, methodParams);
				
				String methodExceptionsData = "";
				for (Class exceptionClazz : method.getMethodExceptions()){
					methodExceptionsData += (exceptionClazz.getCanonicalName() + "|");
				}

				Dimension methodExceptions = new Dimension(methodExceptionsData);
				newCase.putDimension(Constants.Dimension.Method.EXCEPTIONS, methodExceptions);
				
				Dimension methodIsPublic = new Dimension(method.getVisibility() == Visibility.PUBLIC_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PUBLIC, methodIsPublic);
				
				Dimension methodIsProtected = new Dimension(method.getVisibility() == Visibility.PROTECTED_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PROTECTED, methodIsProtected);
				
				Dimension methodIsPrivate = new Dimension(method.getVisibility() == Visibility.PRIVATE_VISIBILITY);
				newCase.putDimension(Constants.Dimension.Method.PRIVATE, methodIsPrivate);
				
				Dimension methodIsAbstract = new Dimension(method.isAbstract());
				newCase.putDimension(Constants.Dimension.Method.ABSTRACT, methodIsAbstract);
				
				Dimension methodIsFinal = new Dimension(method.isFinal());
				newCase.putDimension(Constants.Dimension.Method.FINAL, methodIsFinal);
				
				Dimension methodIsStrictfp = new Dimension(method.isStrictfp());
				newCase.putDimension(Constants.Dimension.Method.STRICTFP, methodIsStrictfp);
				
				Dimension methodIsNative = new Dimension(method.isNative());
				newCase.putDimension(Constants.Dimension.Method.NATIVE, methodIsNative);
				
				Dimension methodIsStatic = new Dimension(method.isStatic());
				newCase.putDimension(Constants.Dimension.Method.STATIC, methodIsStatic);
				
				Dimension methodIsSynchronized = new Dimension(method.isSynchronized());
				newCase.putDimension(Constants.Dimension.Method.SYNCRONIZED, methodIsSynchronized);

				caseList.add(newCase);
			}
		}
		return caseList;
	}
}
